/*--------------------------------------------------------------------------------
 * Adaptive Intelligence - Foundation Framework Class Library
 * Version 2.00
 * 
 * A.I. File Format Header Class
 *      Provides a file header definition for the common A.I. file format.
 *------------------------------------------------------------------------------*/

/*--------------------------------------------------------------------------------
 * System Imports
 *------------------------------------------------------------------------------*/
using System;
using System.IO;
using System.Data;
using System.Xml;

namespace Adaptive.Foundation.IO
{
    /// <summary>
    /// Provides a file header definition for the common A.I. file format.
    /// </summary>
    public sealed class AiFileHeader : DisposableObjectBase
    {
        /*--------------------------------------------------------------------------------
         * Public Event Definitions
         *------------------------------------------------------------------------------*/
        #region Public Event Definitions
        #endregion

        /*--------------------------------------------------------------------------------
         * Private/Public Constants Declarations
         *------------------------------------------------------------------------------*/
        #region Private/Public Constants Declarations
        /// <summary>
        /// Starting byte that indicates the file is an AI-formatted file.
        /// </summary>
        public const byte AIFORMAT_MARKER = 0x2A;

        private const string DEF_APP_NAME = "Adaptive Intelligence Foundation Framework";
        private const string DEF_FILETYPE_NAME = "Adaptive Intelligence File";
        private const string DEF_FILETYPE_DESC = "Default Adaptive Intelligence file format.";

        /// <summary>
        /// Default version number.
        /// </summary>
        private const string DEF_VERSION_NO = "2.0.0.0";

        #endregion

        /*--------------------------------------------------------------------------------
         * Private Member Declarations
         *------------------------------------------------------------------------------*/
        #region Private Member Declarations
        /// <summary>
        /// Header identifier.
        /// </summary>
        private byte _aiHeaderMarker = AIFORMAT_MARKER;
        /// <summary>
        /// Related application version data.
        /// </summary>
        private Version _appVersion;
        /// <summary>
        /// Related file format version data.
        /// </summary>
        private Version _formatVersion;
        /// <summary>
        /// Related generating application name.
        /// </summary>
        private string _applicationName = DEF_APP_NAME;
        /// <summary>
        /// File type name.
        /// </summary>
        private string _fileTypeName = DEF_FILETYPE_NAME;
        /// <summary>
        /// File type description.
        /// </summary>
        private string _fileTypeDescription = DEF_FILETYPE_DESC;
        /// <summary>
        /// Meta-data presence flag.
        /// </summary>
        private bool _metaFlag;
        /// <summary>
        /// Meta-Data container.
        /// </summary>
        private XmlDocument _metaData;
        /// <summary>
        /// Indicates a position in the stream at which the data begins.
        /// </summary>
        private int _endOfHeaderPosition;
        #endregion

        /*--------------------------------------------------------------------------------
         * Constructor / Dispose / Destructor Methods
         *------------------------------------------------------------------------------*/
        #region Constructor / Dispose / Destructor Methods
        /// <summary>
        /// Initializes a new instance of the class.
        /// </summary>
        /// <remarks>
        /// This is the default constructor.
        /// </remarks>
        public AiFileHeader()
            : base()
        {
            _appVersion = new Version(DEF_VERSION_NO);
            _formatVersion = new Version(DEF_VERSION_NO);
            _metaData = new XmlDocument();
        }
        /// <summary>
        /// Initializes a new instance of the class to the specified version data.
        /// </summary>
        /// <param name="applicationVersion">
        /// A <see cref="Version"/> instance used to set the application and file format
        /// data.
        /// </param>
        public AiFileHeader(Version applicationVersion)
        {
            _metaData = new XmlDocument();
            _appVersion = new Version(applicationVersion.ToString());
            _formatVersion = new Version(applicationVersion.ToString()); 
        }
        /// <summary>
        /// Initializes a new instance of the class to the specified version data.
        /// </summary>
        /// <param name="applicationVersion">
        /// A <see cref="Version"/> instance used to set the application version.
        /// </param>
        /// <param name="fileFormatVersion">
        /// A <see cref="Version"/> instance used to set the file format version.
        /// </param>
        public AiFileHeader(Version applicationVersion, Version fileFormatVersion)
        {
            _metaData = new XmlDocument();
            _appVersion = new Version(applicationVersion.ToString());
            _formatVersion = new Version(fileFormatVersion.ToString());
        }
        /// <summary>
        /// Initializes a new instance of the class to the specified version data.
        /// </summary>
        /// <param name="applicationVersion">
        /// A <see cref="Version"/> instance used to set the application version.
        /// </param>
        /// <param name="fileFormatVersion">
        /// A <see cref="Version"/> instance used to set the file format version.
        /// </param>
        /// <param name="metaData">
        /// A parameter to the XML meta data to be included in the file header.
        /// </param>
        public AiFileHeader(Version applicationVersion, Version fileFormatVersion, XmlDocument metaData)
        {
            _metaData = metaData;
            _appVersion = new Version(applicationVersion.ToString());
            _formatVersion = new Version(fileFormatVersion.ToString());
        }
        /// <summary>
        /// Deallocates internally allocated objects.
        /// </summary>
        /// <param name="disposing">
        /// A value indicating whether the object is being disposed.
        /// </param>
        protected override void Dispose(bool disposing)
        {
            if ((!Disposed) && (disposing))
            {
                if (_metaData != null)
                    _metaData.RemoveAll();

            }

            _metaData = null;
            _appVersion = null;
            _formatVersion = null;
            _applicationName = null;
            _fileTypeDescription = null;
            _fileTypeName = null;

            base.Dispose(disposing);
        }
        #endregion

        /*--------------------------------------------------------------------------------
         * Public Properties
         *------------------------------------------------------------------------------*/
        #region Public Properties
        /// <summary>
        /// Gets or sets the name of the related application.
        /// </summary>
        /// <value>
        /// An Application name value.
        /// </value>
        public string ApplicationName
        {
            get { return _applicationName; }
            set
            {
                _applicationName = value;
            }
        }
        /// <summary>
        /// Gets the reference to the related application version instance.
        /// </summary>
        /// <value>
        /// A <see cref="Version"/> instance.
        /// </value>
        public Version ApplicationVersion
        {
            get { return _appVersion; }
        }
        /// <summary>
        /// Gets the last known byte position of the end of the file header
        /// data when loaded from a stream instance.
        /// </summary>
        /// <value>
        /// The byte position to rewind to when reading data, if read from a stream instance.
        /// </value>
        public int EndOfHeaderPosition
        {
            get { return _endOfHeaderPosition; }
        }
        /// <summary>
        /// Gets or sets the name of the file type / format.
        /// </summary>
        /// <value>
        /// A file format or file type name value.
        /// </value>
        public string FileTypeName
        {
            get { return _fileTypeName; }
            set
            {
                _fileTypeName = value;
            }
        }
        /// <summary>
        /// Gets or sets the description of the file format.
        /// </summary>
        /// <value>
        /// A file format description string.
        /// </value>
        public string FileTypeDescription
        {
            get { return _fileTypeDescription; }
            set
            {
                _fileTypeDescription = value;
            }
        }
        /// <summary>
        /// Gets the reference to the related file format version instance.
        /// </summary>
        /// <value>
        /// A <see cref="Version"/> instance.
        /// </value>
        public Version FileFormatVersion
        {
            get { return _formatVersion; }
        }
        /// <summary>
        /// Gets the reference to the XML document containing
        /// the file header metadata.
        /// </summary>
        /// <value>
        /// An <see cref="XmlDocument"/> instance.
        /// </value>
        public XmlDocument Metadata
        {
            get { return _metaData; }
        }
        #endregion

        /*--------------------------------------------------------------------------------
         * Protected Methods / Event Methods
         *------------------------------------------------------------------------------*/
        #region Protected Methods / Event Methods
        #endregion

        /*--------------------------------------------------------------------------------
         * Public Methods / Functions
         *------------------------------------------------------------------------------*/
        #region Public Methods / Functions
        /// <summary>
        /// Clears the contents of the object instance without disposing of the instance.
        /// </summary>
        public void Clear()
        {
            _applicationName = string.Empty;
            _fileTypeDescription = string.Empty;
            _fileTypeName = string.Empty;
            _metaData.RemoveAll();
        }
        /// <summary>
        /// Reads the content of the file header from an open stream.
        /// </summary>
        /// <param name="sourceStream">
        /// A <see cref="Stream"/> instance containing the file header data to be read.
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Occurs when the <i>sourceStream</i> parameter is <b>null</b>.
        /// </exception>
        /// <exception cref="ArgumentOutOfRangeException">
        /// Occurs when the underlying stream cannot be read.
        /// </exception>
        /// <exception cref="AiDataFormatException">
        /// Occurs when the expected data is not read correctly.
        /// </exception>
        public void ReadFromStream(Stream sourceStream)
        {
            BinaryReader reader = null;

            if (sourceStream == null)
                throw new ArgumentNullException();

            reader = new BinaryReader(sourceStream);
            ReadFromStream(reader);
            reader = null;
        }
        /// <summary>
        /// Reads the content of the file header from an open stream.
        /// </summary>
        /// <param name="reader">
        /// A <see cref="BinaryReader"/> instance used to read content from
        /// an open stream.
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Occurs when the <i>reader</i> parameter is <b>null</b>.
        /// </exception>
        /// <exception cref="ArgumentOutOfRangeException">
        /// Occurs when the underlying stream cannot be read.
        /// </exception>
        /// <exception cref="AiDataFormatException">
        /// Occurs when the expected data is not read correctly.
        /// </exception>
        public void ReadFromStream(BinaryReader reader)
        {
            int dataSize = 0;               //Size of XML data to read.
            byte[] xmlBytes = null;         //Content of XML meta data.

            if (reader == null)
                throw new ArgumentNullException();

            if (!reader.BaseStream.CanRead)
                throw new ArgumentOutOfRangeException();

            //Ensure an AI file is being read.
            ReadHeaderByte(reader);

            //Read the application version.
            _appVersion = ReadVersion(reader);

            //Read the file format version.
            _formatVersion = ReadVersion(reader);

            //Try to read the description contents.
            _applicationName = ReaderUtils.ReadString(reader);
            _fileTypeName = ReaderUtils.ReadString(reader);
            _fileTypeDescription = ReaderUtils.ReadString(reader);

            //Try to read the meta-data.
            _metaData = new XmlDocument();
            _metaFlag = ReaderUtils.ReadBoolean(reader);
            if (_metaFlag)
            {
                dataSize = ReaderUtils.ReadInt32(reader);
                xmlBytes = ReaderUtils.ReadBytes(reader, dataSize);
                _metaData.Load(new MemoryStream(xmlBytes));
            }

            //Store the current position.
            _endOfHeaderPosition = (int)reader.BaseStream.Position;
        }
        /// <summary>
        /// Writes the contents of the file header to the open stream.
        /// </summary>
        /// <param name="destinationStream">
        /// A <see cref="Stream"/> instance to be written to.
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Occurs when the <i>writer</i> parameter is <b>null</b>.
        /// </exception>
        /// <exception cref="ArgumentOutOfRangeException">
        /// Occurs when the underlying stream is not writable.
        /// </exception>
        public void WriteToStream(Stream destinationStream)
        {
            BinaryWriter writer = null;

            if (destinationStream == null)
                throw new ArgumentNullException();

            writer = new BinaryWriter(destinationStream);
            WriteToStream(writer);
            writer = null;
        }
        /// <summary>
        /// Writes the contents of the file header to the open stream.
        /// </summary>
        /// <param name="writer">
        /// A <see cref="BinaryWriter"/> instance used to write data to an 
        /// open stream.
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Occurs when the <i>writer</i> parameter is <b>null</b>.
        /// </exception>
        /// <exception cref="ArgumentOutOfRangeException">
        /// Occurs when the underlying stream is not writable.
        /// </exception>
        public void WriteToStream(BinaryWriter writer)
        {
            byte[] metaBytes = null;        //Meta data bytes.

            if (writer == null)
                throw new ArgumentNullException();

            if (!writer.BaseStream.CanWrite)
                throw new ArgumentOutOfRangeException();

            //Write the header identifier.
            writer.Write(_aiHeaderMarker);

            //Write the versioning information.
            WriteVersion(writer, _appVersion);
            WriteVersion(writer, _formatVersion);

            //Write names.
            writer.Write(_applicationName);

            //Write names.
            writer.Write(_fileTypeName);

            //Write names.
            writer.Write(_fileTypeDescription);

            //Write the XML metadata content flag.
            if ((_metaData == null) || (_metaData.ChildNodes.Count == 0))
                writer.Write(false);
            else
            {
                writer.Write(true);
                metaBytes = XmlToBytes(_metaData);
                writer.Write(metaBytes.Length);
                writer.Write(metaBytes);
            }
            writer.Flush();
            
            //Store this position indicator.
            _endOfHeaderPosition = (int)writer.BaseStream.Position;
        }
        #endregion

        /*--------------------------------------------------------------------------------
         * Private Methods / Functions
         *------------------------------------------------------------------------------*/
        #region Private Methods / Functions
        /// <summary>
        /// Converts an XML document to a byte array.
        /// </summary>
        /// <param name="itemToSave">
        /// The <see cref="XmlDocument"/> to be stored.
        /// </param>
        /// <returns>
        /// A byte array containing the XML data.
        /// </returns>
        private byte[] XmlToBytes(XmlDocument itemToSave)
        {
            MemoryStream containerStream = null;            //Data container.
            byte[] returnValue = null;                      //Return value.

            containerStream = new MemoryStream();

            itemToSave.Save(containerStream);
            containerStream.Flush();
            returnValue = containerStream.ToArray();

            containerStream.Close();
            containerStream.Dispose();

            return returnValue;
        }
        /// <summary>
        /// Reads the AI file format header byte.
        /// </summary>
        /// <param name="reader">
        /// A <see cref="BinaryReader"/> instance used to read data from an open stream.
        /// </param>
        private void ReadHeaderByte(BinaryReader reader)
        {
            byte data = 0;

            data = ReaderUtils.ReadByte(reader);
            if (data != AIFORMAT_MARKER)
                throw new AiDataFormatException();
        }
        /// <summary>
        /// Reads a version object from an open file.
        /// </summary>
        /// <param name="reader">
        /// A <see cref="BinaryReader"/> instance used to read data from an open stream.
        /// </param>
        /// <returns>
        /// A new <see cref="Version"/> instance.
        /// </returns>
        private Version ReadVersion(BinaryReader reader)
        {
            Version returnValue = null;
            int major = 0;
            int minor = 0;
            int build = 0;
            int rev = 0;

            major = ReaderUtils.ReadInt32(reader);
            minor = ReaderUtils.ReadInt32(reader);
            build = ReaderUtils.ReadInt32(reader);
            rev = ReaderUtils.ReadInt32(reader);

            returnValue = new Version(major, minor, build, rev);
            return returnValue;
        }
        /// <summary>
        /// Writes a version object to an open file.
        /// </summary>
        /// <param name="writer">
        /// A <see cref="BinaryWriter"/> instance used to write data to an open stream.
        /// </param>
        /// <param name="version">
        /// The <see cref="Version"/> instance to be written.
        /// </param>
        private void WriteVersion(BinaryWriter writer, Version version)
        {
            writer.Write(version.Major);
            writer.Write(version.Minor);
            writer.Write(version.Build);
            writer.Write(version.Revision);
        }
        #endregion

    }
}
